package console

import (
	"strconv"
	"strings"

	"gitee.com/dark.H/dark"
	termbox "github.com/nsf/termbox-go"
)

type AppPanel interface {
	Draw()
	OnKey(k termbox.Event) bool
	ExtendKey(int, func(key, cursor int, now any) bool)
}

type PanelLoc struct {
	X        int
	Y        int
	locStr   dark.Str
	maxRow   int
	maxWidth int
}

func (loc *PanelLoc) Label(some dark.Str, lineColumn ...int) {
	oldX, oldY := loc.X, loc.Y

	some = some.Replace("\n", "\\n").Replace("\r", "\\r")
	if lineColumn == nil {
		loc.Cusor().A(some).Print(true)
		loc.X = oldX
		loc.Y = oldY

	} else {
		line := lineColumn[0]
		column := loc.X
		if len(lineColumn) > 1 {
			column = lineColumn[1]
		}
		loc.Cusor(line, column).A(some).Print(true)
		loc.X = oldX
		loc.Y = oldY

	}
}

func (loc *PanelLoc) Cusor(rowColumn ...int) *PanelLoc {
	if loc.maxWidth == 0 {
		loc.maxRow, loc.maxWidth = GetWindowsSize()
	}
	if loc.Y < 0 {
		loc.Y += loc.maxRow
	}
	if rowColumn == nil {
		// fmt.Println(loc.Y, loc.X)
		// time.Sleep(1 * time.Second)

		loc.locStr = loc.locStr.ANSICursor(loc.Y, loc.X)
	} else {
		row := rowColumn[0]
		col := loc.X
		if len(rowColumn) > 1 {
			col = rowColumn[1]
		}
		if row < 0 {
			row += loc.maxRow
		}
		if col < 0 {
			col += loc.maxWidth
		}
		loc.locStr = loc.locStr.ANSICursor(row, col)
	}
	return loc
}

func (loc *PanelLoc) A(s dark.Str) *PanelLoc {
	loc.locStr += dark.Str(s)
	return loc
}

func (loc *PanelLoc) HiddenCursor() *PanelLoc {
	loc.locStr += "\x1b[?25l"
	return loc
}
func (loc *PanelLoc) ShowCursor() *PanelLoc {
	loc.locStr += "\x1b[?25h"
	return loc
}

func (loc *PanelLoc) Line(i int) *PanelLoc {
	if loc.maxWidth == 0 {
		loc.maxRow, loc.maxWidth = GetWindowsSize()
	}
	if i < 0 {
		i = i + loc.maxRow
	}
	loc.locStr = loc.locStr.ANSICursor(loc.Y+i, loc.X)
	return loc
}

func (loc *PanelLoc) Column(i int) *PanelLoc {
	if loc.maxWidth == 0 {
		loc.maxRow, loc.maxWidth = GetWindowsSize()
	}
	if i < 0 {
		i = i + loc.maxWidth
	}
	loc.locStr = loc.locStr.ANSICursor(loc.Y, loc.X+i)
	return loc
}

func (loc *PanelLoc) Reset() *PanelLoc {
	if loc.maxWidth == 0 {
		loc.maxRow, loc.maxWidth = GetWindowsSize()
	}
	if loc.Y < 0 {
		loc.Y = loc.Y + loc.maxRow
	}

	loc.locStr += loc.locStr.ANSI("H")
	// loc.locStr += loc.locStr.ANSICursor(0, 0)
	// loc.locStr += dark.Str(strings.Repeat("\n", loc.maxRow-3))

	// loc.locStr = loc.locStr.ANSIEraseToEND()

	return loc
}

func (loc *PanelLoc) String() string {
	return loc.locStr.Str()
}

func (loc *PanelLoc) Str() dark.Str {
	return loc.locStr
}

func (loc *PanelLoc) Print(resetStr ...bool) string {
	loc.locStr.Print()
	e := loc.locStr.Str()
	if resetStr != nil && resetStr[0] {
		loc.locStr = dark.Str("")
	}
	return e
}

func (loc *PanelLoc) Printline(resetStr ...bool) string {
	dark.Str("").ANSIClearThisLine().Add(loc.locStr).Print()
	e := loc.locStr.Str()
	if resetStr != nil && resetStr[0] {
		loc.locStr = dark.Str("")
	}
	return e
}

type ListPanel[T any] struct {
	ShowBar bool
	ShowNum bool
	Loc     *PanelLoc
	Size    int
	Window  int
	Cursor  int
	Pos     int
	keyNow  int
	Mode    int

	Items      dark.List[T]
	ExtendKeys map[int]func(key, cursor int, now any) bool
	Selected   T
	Label      string
	savedKeys  string
	SearchKey  dark.Str
}

func NewListPanel[T any](label string, items dark.List[T], windowSize int) (p *ListPanel[T]) {
	return &ListPanel[T]{

		Items: items,
		Loc: &PanelLoc{
			X: 1,
			Y: 1,
		},
		Size:   items.Count(),
		Label:  label,
		Window: windowSize,

		ExtendKeys: make(map[int]func(key int, cursor int, now any) bool),
		ShowBar:    true,
		ShowNum:    true,
		Selected:   items[0],
	}
}
func (draw *ListPanel[T]) SetTopOffset(i int) {
	draw.Loc.Y += i
}
func (draw *ListPanel[T]) SetLeftOffset(i int) {
	draw.Loc.X += i
}

func (draw *ListPanel[T]) Add(one T) {
	draw.Items = draw.Items.Add(one)
	draw.Size += 1
}

func (draw *ListPanel[T]) WithSubPanel(subpanelDo func()) *ListPanel[T] {

	defer func() {
		termbox.Init()
		draw.Mode = 0
		draw.Draw()
	}()
	draw.Mode = 1
	subpanelDo()
	return draw
}

func (draw *ListPanel[T]) Draw() {
	if draw.Size == 0 {
		return
	}
	draw.Loc.maxRow, draw.Loc.maxWidth = GetWindowsSize()
	bar := int(draw.Window * draw.Window / draw.Size)
	bar_start := int(draw.Pos * draw.Window / draw.Size)
	bar_end := bar_start + bar
	draw_num := 0

	draw.Loc.Cusor().Reset().A(dark.Str(draw.Label).Color("g") + "  line:" + dark.S(draw.Cursor).Color("y", "F") + " key:" + dark.S(draw.keyNow)).Print(true)

	draw.Items.Every(func(no int, i T) {
		// fmt.Println("i", i)
		if no < draw.Pos {
			return
		}

		if no >= draw.Pos+draw.Window {
			return
		}
		// fmt.Println("ok")
		pre := " "
		if draw_num >= bar_start && draw_num <= bar_end && draw.ShowBar {

			if draw_num == bar_start {
				pre = "╦"
			} else if draw_num == bar_end {
				pre = "╩"
			} else {
				pre = "║"
			}
		}
		mid := ""
		if draw.ShowNum {
			mid = dark.Str(" %3d ").F(no).Color("B").Str()
		}
		if no == draw.Cursor {
			w := draw.Loc.Line(draw_num+1).Str() + dark.Str(pre+" %s%s").F(mid, dark.S(i).Color("B", "U", "g"))
			if draw.Loc.maxWidth-w.Len() > 5 {
				w += dark.Str(strings.Repeat(" ", draw.Loc.maxWidth-w.Len()-5))
			}
			(w).Print(true)
			draw.Loc.locStr = dark.Str("")

		} else {
			w := draw.Loc.Line(draw_num+1).Str() + dark.Str(pre+" %s%s").F(mid, i)
			if draw.Loc.maxWidth-w.Len() > 5 {
				w += dark.Str(strings.Repeat(" ", draw.Loc.maxWidth-w.Len()-5))
			}
			(w).Print(true)
			draw.Loc.locStr = dark.Str("")
			// dark.S(i).Println()
		}
		draw_num += 1
	})
}

func (draw *ListPanel[T]) Down() {
	draw.Cursor += 1
	if draw.Cursor == draw.Size {
		draw.Cursor = 0
		draw.Pos = 0
	}
	// c = 10 , pos + 1
	if draw.Cursor >= draw.Window+draw.Pos {

		draw.Pos += 1
	}
}

func (draw *ListPanel[T]) Up() {
	draw.Cursor -= 1
	if draw.Cursor < 0 {
		draw.Cursor = draw.Size + draw.Cursor
		draw.Pos = draw.Cursor - draw.Window + 1
	}
	if draw.Cursor < draw.Pos {
		draw.Pos -= 1
	}
}

func (draw *ListPanel[T]) SetLabel(msg dark.Str) {
	draw.Label = msg.Str()
}

func (draw *ListPanel[T]) OnKey(k termbox.Event) bool {
	// suffix = dark.Str("")
	draw.keyNow = int(k.Key)
	// fmt.Println(k)
	// fmt.Println(k)
	// time.Sleep(3 * time.Second)
	if k.Key == termbox.KeyEnter {
		return true
	}

	switch k.Ch {
	case 'j':
		if draw.savedKeys == "" {
			draw.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(draw.savedKeys)
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			draw.Down()
		}
		draw.Selected = draw.Items[draw.Cursor]
		// now = draw.Cursor
		draw.savedKeys = ""
	case '/':
		k := InputLastLine()
		termbox.Init()
		draw.SearchKey = k
		now := draw.Cursor
		e := draw.Cursor
		draw.Items[now:].Any(func(no int, i T) bool {
			if dark.S(i).In(k.Str()) {
				e = no + now
				return true
			}
			return false
		})
		if e > draw.Cursor {
			for i := 0; e != draw.Cursor; i++ {
				draw.Down()
			}
			draw.Selected = draw.Items[draw.Cursor]
		} else if e < draw.Cursor {
			for i := 0; e != draw.Cursor; i++ {
				draw.Up()
			}
			draw.Selected = draw.Items[draw.Cursor]
		}

	case 'n':
		k := draw.SearchKey
		e := draw.Cursor
		now := draw.Cursor
		draw.Items[now:].Any(func(no int, i T) bool {
			if dark.S(i).In(k.Str()) {
				e = no + now
				return true
			}
			return false
		})
		if e > draw.Cursor {
			for i := 0; i < e-draw.Cursor; i++ {
				draw.Down()
			}
			draw.Selected = draw.Items[draw.Cursor]
		} else if e < draw.Cursor {
			for i := 0; i < draw.Cursor-e; i++ {
				draw.Up()
			}
			draw.Selected = draw.Items[draw.Cursor]
		}

	case 'k':
		if draw.savedKeys == "" {
			draw.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(draw.savedKeys)
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			draw.Up()
		}

		draw.Selected = draw.Items[draw.Cursor]
		// now = panel.Cursor
		draw.savedKeys = ""

	case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0':
		draw.savedKeys += string(k.Ch)

	default:
		if call, ok := draw.ExtendKeys[int(k.Key)]; ok {
			if call(int(k.Key), draw.Cursor, draw.Items[draw.Cursor]) {
				// fmt.Println(k)
				// time.Sleep(5 * time.Second)
				return true
			}
		} else {
			switch k.Key {
			case 13:
				draw.Selected = draw.Items[draw.Cursor]
				return true
			case 14:

				ecou := draw.Window
				for i := 0; i < ecou; i++ {
					draw.Down()
				}

				// now = draw.Cursor
				draw.savedKeys = ""

			case 16:
				ecou := draw.Window
				for i := 0; i < ecou; i++ {
					draw.Up()
				}

				// now = draw.Cursor
				draw.savedKeys = ""
			default:
				draw.SetLabel(dark.S(k))

			}
		}
		// suffix = dark.Str(" ( %s %d [%s] ) ").F(dark.Str(k.Ch).Color("B", "y"), k.Ch, fmt.Sprint(k))
	}

	draw.Draw()
	return false

}

func (panel *ListPanel[T]) ExtendKey(k int, c func(key, cursor int, now any) bool) {
	panel.ExtendKeys[k] = c
}
